package com.example.springbootwebserviceproxy.filter;

import cn.hutool.core.util.StrUtil;
import com.example.springbootwebserviceproxy.model.SOAPServiceProxyDefinition;
import com.example.springbootwebserviceproxy.model.WebServiceBeanDefinition;
import com.example.springbootwebserviceproxy.pool.SOAPServiceFactory;
import com.example.springbootwebserviceproxy.util.Assert;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.cxf.helpers.IOUtils;
import org.apache.http.Header;
import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpOptions;
import org.apache.http.client.methods.HttpPatch;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.client.methods.HttpTrace;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.entity.InputStreamEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;


import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Enumeration;

import static com.example.springbootwebserviceproxy.filter.WebServiceProxyFilter.URI_PREFIX;


/**
 * HutoolSOAPApiProxyImpl.
 *
 * @author <a href='mailto:likeguo@apache.com'> likeguo </a>
 */
@Slf4j
@WebFilter(urlPatterns = URI_PREFIX + "/*")
@RequiredArgsConstructor
public class WebServiceProxyFilter implements Filter {
    /**
     * 代理前缀
     */
    public static final String URI_PREFIX = "/soap/proxy";
    
    private final SOAPServiceFactory soapServiceFactory;
    
    
    @Override
    public void doFilter(final ServletRequest req, final ServletResponse resp, final FilterChain chain) throws IOException {
        try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
            // 获取代理请求
            HttpUriRequest proxyRequest = proxyRequest((HttpServletRequest) req);
            // 提交soap代理请求
            final CloseableHttpResponse proxyResponses = httpClient.execute(proxyRequest);
            // 处理代理请求
            doReturn((HttpServletResponse) resp, proxyResponses);
        } catch (Exception e) {
            doError(resp, e);
        }
    }
    
    private HttpUriRequest proxyRequest(HttpServletRequest request) throws IOException {
        final WebServiceBeanDefinition definition = getProxyDefinition(request);
        HttpRequestBase proxyRequest = build(request, definition);
        copyHeader(request, proxyRequest, definition);
        if (proxyRequest instanceof HttpEntityEnclosingRequest) {
            //构造新的请求数据流
            ((HttpEntityEnclosingRequest) proxyRequest).setEntity(new InputStreamEntity(request.getInputStream()));
        }
        return proxyRequest;
    }
    
    private WebServiceBeanDefinition getProxyDefinition(final HttpServletRequest request) {
        final String uri = request.getRequestURI();
        Assert.notBack(uri, "代理服务器位置为空请指定代理服务器位置");
        final String targetUri = StrUtil.removePrefixIgnoreCase(uri, URI_PREFIX);
        final WebServiceBeanDefinition definition = soapServiceFactory.getServiceByName(targetUri);
        Assert.notNull(definition, "未知的代理服务");
        log.info("soap代理：原地址【{}】目标地址【{}】", uri, definition.getProxyDefinition().getTargetUri());
        return definition;
    }
    
    private HttpRequestBase build(final HttpServletRequest request, final WebServiceBeanDefinition definition) {
        final String method = request.getMethod();
        switch (method) {
            case HttpGet.METHOD_NAME:
                return new HttpGet(definition.getTargetUrl());
            case HttpPut.METHOD_NAME:
                return new HttpPut(definition.getTargetUrl());
            case HttpDelete.METHOD_NAME:
                return new HttpDelete(definition.getTargetUrl());
            case HttpPatch.METHOD_NAME:
                return new HttpPatch(definition.getTargetUrl());
            case HttpTrace.METHOD_NAME:
                return new HttpTrace(definition.getTargetUrl());
            case HttpOptions.METHOD_NAME:
                return new HttpOptions(definition.getTargetUrl());
            default:
                return new HttpPost(definition.getTargetUrl());
        }
    }
    
    private void copyHeader(final HttpServletRequest request, final HttpRequestBase proxyRequest, WebServiceBeanDefinition definition) {
        //复制请求参数
        final Enumeration<String> headers = request.getHeaderNames();
        while (headers.hasMoreElements()) {
            String name = headers.nextElement();
            //注意这里不要设置，发送请求会抛异常
            if ("Content-Length".equalsIgnoreCase(name)) {
                continue;
            }
            proxyRequest.setHeader(name, request.getHeader(name));
        }
        final SOAPServiceProxyDefinition proxyDefinition = definition.getProxyDefinition();
        proxyRequest.setHeader("host", proxyDefinition.getHost());
    }
    
    private void doReturn(final HttpServletResponse response, final CloseableHttpResponse httpResponse) throws IOException {
        //复制soap相应的消息头
        for (Header h : httpResponse.getAllHeaders()) {
            response.setHeader(h.getName(), h.getValue());
        }
        //讲soap的返回流复制到filter的输出流
        IOUtils.copyAndCloseInput(httpResponse.getEntity().getContent(), response.getOutputStream());
    }
    
    
    @Override
    public void init(final FilterConfig filterConfig) throws ServletException {
        log.info("WebServiceDispatchFilter init ,filter:{}", filterConfig.getFilterName());
    }
    
    
    @Override
    public void destroy() {
        log.info("WebServiceDispatchFilter destroy");
    }
    
    private void doError(final ServletResponse response, final Exception e) throws IOException {
        e.printStackTrace();
        response.setContentType("text/xml;charset=UTF-8");
        String result = "<soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">"
                + "<soap:Body><soap:Fault><faultcode>"
                + "500"
                + "</faultcode>"
                + "<faultstring>"
                + "失败:"
                + e.getMessage()
                + "</faultstring></soap:Fault></soap:Body></soap:Envelope>";
        response.getWriter().print(result);
    }
}
